package top.dotgo.service.base;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.TypeUtil;
import com.querydsl.core.QueryResults;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.*;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.Getter;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageRequest;
import top.dotgo.dao.base.BaseDao;
import top.dotgo.dao.base.Q;
import top.dotgo.kit.bean.Page;
import top.dotgo.model.BaseQuery;
import top.dotgo.model.IBaseDTO;
import top.dotgo.model.IBaseEntity;
import top.dotgo.model.IBaseMapping;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

/**
 * @author : lin
 * @date : 2019/10/11 18:37
 */
@CacheConfig(cacheNames = "ServiceData", keyGenerator = "keyGenerator")
public class BaseServiceImpl<T extends IBaseEntity, D extends IBaseDTO<T>, P> implements IBaseService<T, D, P> {
//    @Getter
//    private final JPAQueryFactory factory;

    @Getter
    private final BaseDao<T, P> baseDao;

    @Getter
    private final IBaseMapping<D, ?, ?, ?, T> mapping;

    public BaseServiceImpl(BaseDao<T, P> baseDao, IBaseMapping<D, ?, ?, ?, T> mapping) {
        this.baseDao = baseDao;
        this.mapping = mapping;
    }

    @CacheEvict
    @Override
    public D save(D obj) {
        T entity = mapping.entity(obj);

        entity = baseDao.save(entity);

        return mapping.dto(entity);
    }

    @CacheEvict
    @Override
    public List<D> save(List<D> list) {
        List<D> temp = new ArrayList<>();
        for (D D : list) {
            temp.add(save(D));
        }
        return temp;
    }

    @CacheEvict
    @Override
    public D saveAndFlush(D obj) {
        T entity = mapping.entity(obj);

        entity = baseDao.saveAndFlush(entity);

        return mapping.dto(entity);
    }

    @CacheEvict
    @Override
    public void updateById(D obj) {
        updateById(obj, (Path<?>) null);
    }

    @CacheEvict
    @Override
    public D updateById(D obj, Path<?>... nullProperties) {
        T entity = mapping.entity(obj);

        P pk = Q.getPkValue(entity);
        if (pk == null) {
            return null;
        }
        T temp = baseDao.findById(pk).orElse(null);
        if (temp == null) {
            return null;
        }
        BeanUtil.copyProperties(obj, temp, CopyOptions.create().ignoreNullValue());

        if (nullProperties != null && nullProperties.length > 0 && nullProperties[0] != null) {
            for (Path<?> property : nullProperties) {
                BeanUtil.setFieldValue(temp, property.getMetadata().getName(), null);
            }
        }
        entity = baseDao.saveAndFlush(entity);

        return mapping.dto(entity);
    }

    @CacheEvict
    @Override
    public long updateByWhere(D obj, Predicate... predicates) {
        return updateByWhere(obj, null, predicates);
    }

    @CacheEvict
    @Override
    public long updateByWhere(D obj, List<Path<?>> nullProperties, Predicate... predicates) {
        T entity = mapping.entity(obj);

        return baseDao.updateByWhere(entity, nullProperties, predicates);
    }

    @SuppressWarnings("unchecked")
    @CacheEvict
    @Override
    public void delete(Serializable id) {
        baseDao.deleteById((P) id);
    }

    @CacheEvict
    @Override
    public long delete(List<P> ids) {
        long count = 0;
        for (P id : ids) {
            baseDao.deleteById(id);
            count++;
        }
        return count;
    }

    @CacheEvict
    @Override
    public long delete(D obj) {
        T entity = mapping.entity(obj);

        return delete(Q.getPredicates(entity));
    }

    @CacheEvict
    @Override
    public long delete(Predicate... predicates) {
        return baseDao.delete(getClazz(), predicates);
    }

    @SuppressWarnings("unchecked")
    @Override
    public long count(Serializable id) {
        return baseDao.existsById((P) id) ? 1 : 0;
    }

    @Override
    public long count(D obj) {
        T entity = mapping.entity(obj);

        return count(Q.getPredicates(entity));
    }

    @Override
    public long count(Predicate... predicates) {
        return baseDao.count(getClazz(), predicates);
    }

    @Cacheable
    @Override
    public D find(Serializable id) {
        return find(id, Q.expression(getClazz()));
    }

    @Cacheable
    @Override
    public D find(Serializable id, Path<?>... paths) {
        return find(id, Projections.fields(getClazz(), paths));
    }

    @Cacheable
    @Override
    public D find(Serializable id, Expression<T> expression) {
        Class<T> clazz = getClazz();
        T bean = Q.getBean(clazz);
        String pk = Q.getPkName(clazz);
        if (pk == null) {
            return null;
        }
        BeanUtil.setFieldValue(bean, pk, id);
        Predicate[] predicates = Q.getPredicates(bean);
        return findFirst(predicates != null ? Arrays.asList(predicates) : null);
    }

    @Cacheable
    @Override
    public D findFirst(D obj, OrderSpecifier<?>... orders) {
        T entity = mapping.entity(obj);

        Predicate[] predicates = Q.getPredicates(entity);
        return findFirst(predicates != null ? Arrays.asList(predicates) : null, orders);
    }

    @Cacheable
    @Override
    public D findFirst(D obj, List<Path<?>> paths, OrderSpecifier<?>... orders) {
        T entity = mapping.entity(obj);

        Predicate[] predicates = Q.getPredicates(entity);
        return findFirst(paths, predicates != null ? Arrays.asList(predicates) : null, orders);
    }

    @Cacheable
    @Override
    public D findFirst(List<Predicate> predicates, OrderSpecifier<?>... orders) {
        return findFirst(Q.expression(getClazz()), predicates, orders);
    }

    @Cacheable
    @Override
    public D findFirst(List<Path<?>> paths, List<Predicate> predicates, OrderSpecifier<?>... orders) {
        return findFirst(Projections.fields(getClazz(), paths.toArray(new Path[0])), predicates, orders);
    }

    @Cacheable
    @Override
    public D findFirst(Expression<T> expression, List<Predicate> predicates, OrderSpecifier<?>... orders) {
        T entity = baseDao.findFirst(getClazz(), expression, predicates, orders);
        return mapping.dto(entity);
    }

    @Cacheable
    @Override
    public Page<D> findPage(int page, int size) {
        org.springframework.data.domain.Page<T> result = baseDao.findAll(PageRequest.of(page, size));
        return new Page<D>().setTotal(result.getTotalPages())
                .setCount((int) result.getTotalElements())
                .setSize(result.getSize())
                .setEmpty(result.isEmpty())
                .setPage(page)
                .setList(mapping.dto(result.getContent()));
    }

    @Cacheable
    @Override
    public Page<D> findPage(BaseQuery<T> queryBean) {
        Predicate[] predicates = Q.getPredicates(queryBean);
        return findPage(predicates != null ? Arrays.asList(predicates) : null, queryBean.getPage(), queryBean.getSize(), Q.getOrder(queryBean));
    }

    @Cacheable
    @Override
    public Page<D> findPage(JPAQuery<Tuple> query, BaseQuery<T> queryBean, Function<Tuple, D> convert) {
        //拼接条件
        Predicate[] predicates = Q.getPredicates(queryBean);
        return findPage(query, predicates != null ? Arrays.asList(predicates) : null, queryBean.getPage(), queryBean.getSize(), convert, Q.getOrder(queryBean));
    }

    @Cacheable
    @Override
    public Page<D> findPage(JPAQuery<Tuple> query, List<Predicate> predicates, int page, int size, Function<Tuple, D> convert, OrderSpecifier<?>... orders) {
        if (predicates != null) {
            query.where(predicates.toArray(new Predicate[0]));
        }
        //分页
        query.offset(Q.offset(page, size)).limit(size);

        if (orders != null && orders.length > 0 && orders[0] != null) {
            for (OrderSpecifier<?> order : orders) {
                query.orderBy(order);
            }
        }

        QueryResults<Tuple> results = query.fetchResults();
        return Q.getPage(results, convert);
    }

    @Cacheable
    @Override
    public Page<D> findPage(D obj, int page, int size, OrderSpecifier<?>... orders) {
        T entity = mapping.entity(obj);

        Predicate[] predicates = Q.getPredicates(entity);
        return findPage(Q.expression(getClazz()), predicates != null ? Arrays.asList(predicates) : null, page, size, orders);
    }

    @Cacheable
    @Override
    public Page<D> findPage(List<Predicate> predicates, int page, int size, OrderSpecifier<?>... orders) {
        return findPage(Q.expression(getClazz()), predicates, page, size, orders);
    }

    @Cacheable
    @Override
    public Page<D> findPage(List<Path<?>> paths, List<Predicate> predicates, int page, int size, OrderSpecifier<?>... orders) {
        return findPage(Projections.fields(getClazz(), paths.toArray(new Path[0])), predicates, page, size, orders);
    }

    @Cacheable
    @Override
    public Page<D> findPage(Expression<T> expression, List<Predicate> predicates, int page, int size, OrderSpecifier<?>... orders) {
        Page<T> pageEntity = baseDao.findPage(getClazz(), expression, predicates, page, size, orders);
        Page<D> pageResult = new Page<>();
        pageResult.setPage(pageEntity.getPage())
                .setSize(pageEntity.getSize())
                .setCount(pageEntity.getCount())
                .setTotal(pageEntity.getTotal())
                .setEmpty(pageEntity.isEmpty())
                .setList(mapping.dto(pageEntity.getList()));

        return pageResult;
    }

    @Cacheable
    @Override
    public List<D> findList(OrderSpecifier<?>... orders) {
        return findList(Q.expression(getClazz()), null, orders);
    }

    @Cacheable
    @Override
    public List<D> findList(List<P> ids) {
        return mapping.dto(baseDao.findAllById(ids));
    }

    @Cacheable
    @Override
    public List<D> findList(BaseQuery<T> queryBean) {
        Predicate[] predicates = Q.getPredicates(queryBean);
        return findList(predicates != null ? Arrays.asList(predicates) : null, Q.getOrder(queryBean));
    }

    @Cacheable
    @Override
    public Page<D> findList(JPAQuery<Tuple> query, BaseQuery<T> queryBean, Function<Tuple, D> convert) {
        //拼接条件
        Predicate[] predicates = Q.getPredicates(queryBean);
        return findList(query, predicates != null ? Arrays.asList(predicates) : null, convert, Q.getOrder(queryBean));
    }

    @Cacheable
    @Override
    public Page<D> findList(JPAQuery<Tuple> query, List<Predicate> predicates, Function<Tuple, D> convert, OrderSpecifier<?>... orders) {
        if (predicates != null) {
            query.where(predicates.toArray(new Predicate[0]));
        }

        if (orders != null && orders.length > 0 && orders[0] != null) {
            for (OrderSpecifier<?> order : orders) {
                query.orderBy(order);
            }
        }

        QueryResults<Tuple> results = query.fetchResults();
        return Q.getPage(results, convert);
    }

    @Cacheable
    @Override
    public List<D> findList(D obj, OrderSpecifier<?>... orders) {
        T entity = mapping.entity(obj);

        Predicate[] predicates = Q.getPredicates(entity);
        return findList(Q.expression(getClazz()), predicates != null ? Arrays.asList(predicates) : null, orders);
    }

    @Cacheable
    @Override
    public List<D> findList(List<Predicate> predicates, OrderSpecifier<?>... orders) {
        return findList(Q.expression(getClazz()), predicates, orders);
    }

    @Cacheable
    @Override
    public List<D> findList(List<Path<?>> paths, List<Predicate> predicates, OrderSpecifier<?>... orders) {
        return findList(Projections.fields(getClazz(), paths.toArray(new Path[0])), predicates, orders);
    }

    @Cacheable
    @Override
    public List<D> findList(Expression<T> expression, List<Predicate> predicates, OrderSpecifier<?>... orders) {
        return mapping.dto(baseDao.findList(getClazz(), expression, predicates, orders));
    }

    @CacheEvict
    @Override
    public void flush() {
        baseDao.flush();
    }

    protected JPAQueryFactory getFactory() {
        return BaseDao.FACTORY;
    }


    @SuppressWarnings("unchecked")
    private Class<T> getClazz() {
        return (Class<T>) TypeUtil.getTypeArgument(getClass());
    }

}
